const util = require("../lib/util");
const moduleMap = require("../modules").base;

let typeMap;

class Base {

    key;  //块键名
    expression;  //块表达式
    map = {};  //值映射
    data = {};  //值数据
    #children = [];  //子节点

    constructor(key, expression) {
        this.key = key;
        this.expression = expression;
        typeMap = require("./type_map");
    }

    init(options = {}) {
        const map = { ...(moduleMap || {}), ...this.map }
        for(let key in map) {
            const [type] = map[key];
            const descriptor = {
                get() {
                    return this.data[key];
                },
                set(value) {
                    const parse = (_value) => {
                        try {
                            if(util.isArray(type)) {
                                for(let _type of type) {
                                    let value = _value;
                                    try {
                                        value = _type(value);
                                        if(util.isNaN(value)) throw new TypeError();
                                        return value;
                                    } catch(e) {}
                                }
                                throw new TypeError();
                            }
                            else {
                                _value = type(_value);
                                if(util.isNaN(_value)) throw new TypeError();
                            }
                        }
                        catch(err) {
                            throw new TypeError(`attribute ${key} must be ${util.isArray(type) ? type.map(_type => _type.name).join("/") : type.name} type: ${err.message}`);
                        }
                        return _value;
                    }
                    if(util.isArray(value))
                        this.data[key] = value.map(v => parse(v));
                    else
                        this.data[key] = parse(value);
                }
            }
            Object.defineProperty(this, key, descriptor);
            const camelCaseKey = util.camelCase(key);
            camelCaseKey !== key && Object.defineProperty(this, camelCaseKey, descriptor);
        }
        Object.assign(this, options);
    }

    append(node) {
        this.#children.push(node);
    }

    build(span = "") {
        const space = this.key ? "    " : "";
        let block = this.key ? `\n${span}${this.key}${this.expression ? " " + this.expression : ""} {` : "";
        for(let key in this.data) {
            if(util.isNil(this[key])) continue;
            if(util.isArray(this[key]))
                this[key].forEach(value => block += `\n${span}${space}${key}  ${value.toString()};`);
            else
                block += `\n${span}${space}${key}  ${this[key].toString()};`;
        };
        this.#children.forEach(node => {
            const chunk = node.build(span + space);
            block += chunk ? `\n${chunk}` : "";
        });
        block += (this.key ? `\n${span}}` : "");
        return block;
    }

    set children(value) {
        this.#children = value.map(v => v.expression ? new typeMap[v.type](v.expression, v) : new typeMap[v.type](v));
    }

}

module.exports = Base;